// VectoriseCPP.cpp : main project file.

#include <sstream>
#include <math.h>
#include <cstdio>
#include <iostream>

#if defined(__INTEL_COMPILER) || defined(__GNUC__)
#undef BOOSTTIMER
#else
#define BOOSTTIMER
#endif

#ifdef BOOSTTIMER
#include <boost/chrono.hpp>
#elif defined(__GNUC__)
#include <ctime>
#else
#include <windows.h> 
#endif

#if defined(__INTEL_COMPILER)
std::string compiler = "Intel";
#elif defined(__GNUC__)
std::string compiler = "gcc";
#elif defined(_MSC_VER)
std::string compiler = "VS2013";
#endif

#include "Paths.h"

#define VOLTARGET VolTargetO0Off
#include "VolTarget.h"
#undef VOLTARGET
#define VOLTARGET VolTargetO1Size
#include "VolTarget.h"
#undef VOLTARGET
#define VOLTARGET VolTargetO2Speed
#include "VolTarget.h"
#undef VOLTARGET
#define VOLTARGET VolTargetOxFull
#include "VolTarget.h"
#undef VOLTARGET
#define VOLTARGET VolTarget4SSE2
#include "VolTarget.h"
#undef VOLTARGET
#define VOLTARGET VolTarget5AVX
#include "VolTarget.h"
#undef VOLTARGET
#define VOLTARGET VolTarget6FpFast
#include "VolTarget.h"
#undef VOLTARGET
#define VOLTARGET VolTarget7Vec
#include "VolTarget7Vec.h"
#undef VOLTARGET

#ifdef BOOSTTIMER
typedef boost::chrono::process_cpu_clock Clock;
#elif !defined(__GNUC__)
__int64 getWallClockMilliseconds()
{
    const __int64 HundredNanosecondsToMilliseconds = 10000;
    FILETIME systemTimeAsFileTime;
    GetSystemTimeAsFileTime(&systemTimeAsFileTime);
    __int64 time;
    time = systemTimeAsFileTime.dwLowDateTime | ((__int64)systemTimeAsFileTime.dwHighDateTime) << 32;
    return time / HundredNanosecondsToMilliseconds;
}
#endif

int main()
{
    int trials = 13000;
    int runs = 10;
    int obs = 1424;
    int unds = 5;
    std::cout << "C++ " << compiler << " trials=" << trials << " nUnd=" << unds << " nObs=" << obs << " order=" << trials*unds*obs << std::endl;
    EQMCBase* payoff = NULL;
    std::string name;

    for (int test = 7; test >= 0; --test)
    {
        //if (test  5) continue; //skip AVX on my laptop
        if (test < 3) continue; 
        delete payoff;
        switch (test)
        {
        case 0:
            name = "VolTargetO0Off";
            payoff = new VolTargetO0Off();
            payoff->init(trials, unds, obs);
            payoff->initData();
            break;
        case 1:
            name = "VolTargetO1Size";
            payoff = new VolTargetO1Size();
            payoff->init(trials, unds, obs);
            payoff->initData();
            break;
        case 2:
            name = "VolTargetO2Speed";
            payoff = new VolTargetO2Speed();
            payoff->init(trials, unds, obs);
            payoff->initData();
            break;
        case 3:
            name = "VolTargetOxFull";
            payoff = new VolTargetOxFull();
            payoff->init(trials, unds, obs);
            payoff->initData();
            break;
        case 4:
            name = "VolTarget4SSE2";
            payoff = new VolTarget4SSE2();
            payoff->init(trials, unds, obs);
            payoff->initData();
            break;
        case 5:
            name = "VolTarget5AVX";
            payoff = new VolTarget5AVX();
            payoff->init(trials, unds, obs);
            payoff->initData();
            break;
        case 6:
            name = "VolTarget6FpFast";
            payoff = new VolTarget6FpFast();
            payoff->init(trials, unds, obs);
            payoff->initData();
            break;
        case 7:
            name = "VolTarget7Vec";
            payoff = new VolTarget7Vec();
            payoff->init(trials, unds, obs);
            payoff->initData();
            break;
        }
        int sum = 0, sumSq = 0;
        for (int run = 0; run < runs; ++run)
        {
#ifdef BOOSTTIMER
            auto time = Clock::now();
#elif defined(__GNUC__)
            clock_t time = clock();
#else
            __int64 time = getWallClockMilliseconds();
#endif
            double pv = payoff->run();
            //std::cout << pv << " ";
#ifdef BOOSTTIMER
            int ms = static_cast<int>(boost::chrono::duration_cast<boost::chrono::milliseconds>(Clock::now() - time).count());
#elif defined(__GNUC__)
            int ms = clock() - time;
#else
            int ms = getWallClockMilliseconds() - time;
#endif
            sum += ms;
            sumSq += ms*ms;
        }
        double mean = sum / runs;
        double sd = sqrt(sumSq / runs - mean*mean);
        std::cout << name << "    \t" << mean << " +/- " << sd << std::endl;
    }

    return 0;
}

std::string to_string(double d)
{
    std::stringstream s;
    s << d;
    return s.str();
}

void EQMCBase::init(int trials_, int unds_, int obs_)
{
    trials = trials_;
    nUnds = unds_;
    nObs = obs_;
    nSch2s = 13;
    path = new double[trials_ * nUnds * (nObs + 1)];
#if !defined(__GNUC__)
    std::default_random_engine generator;
    std::normal_distribution<double> distribution(0.0, 1.0);
#endif
    for (int i = 0; i < trials_; ++i)
        for (int u = 0; u < unds_; ++u)
            for (int t = 0; t <= obs_; ++t)
#if !defined(__GNUC__)
                setNS(i, u, t) = 100 + sqrt(t) * distribution(generator);
#else
                setNS*(i, u, t) = 100;
#endif
    // SCH1 = "sch1";
    //SCH2 = "sch2";
    strikeFactor = 1.0;
    derivativeType = CALL;
}

int EQMCBase::getInt(const string& name, int def) const
{
    if (name == "nReturns")
        return 50;
    return def;
}
double EQMCBase::getDouble(const string& name, double def) const
{
    return def;
}
string EQMCBase::getString(const string& name, const string& def) const
{
    return def;
}
double EQMCBase::yearFraction(int t1, int t2, const string& dayCountBasis) const
{
    return 1.0;
}
